package com.daikit.daikit4gxt.client.ui.editor.field;


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.daikit.commons.shared.utils.DkStringUtils;
import com.daikit.daikit4gxt.client.ui.component.DkSpacer;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorDelegate;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.editor.client.ValueAwareEditor;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.user.client.ui.InlineLabel;
import com.sencha.gxt.messages.client.DefaultMessages;
import com.sencha.gxt.widget.core.client.container.FlowLayoutContainer;
import com.sencha.gxt.widget.core.client.container.HBoxLayoutContainer;
import com.sencha.gxt.widget.core.client.event.BlurEvent;
import com.sencha.gxt.widget.core.client.event.BlurEvent.BlurHandler;
import com.sencha.gxt.widget.core.client.form.AdapterField;
import com.sencha.gxt.widget.core.client.form.Field;
import com.sencha.gxt.widget.core.client.form.Validator;
import com.sencha.gxt.widget.core.client.form.error.DefaultEditorError;
import com.sencha.gxt.widget.core.client.form.error.SideErrorHandler;


/**
 * An Editor allowing to edit localized fields
 * 
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 * @param <KEY_TYPE>
 *           The key type of the localized map
 * @param <VALUE_TYPE>
 *           The value type of the localized map (normally it should be a String , but it can be any other object type)
 */
public abstract class DkLocalizedField<KEY_TYPE, VALUE_TYPE> extends AdapterField<Map<KEY_TYPE, VALUE_TYPE>> implements
		ValueAwareEditor<Map<KEY_TYPE, VALUE_TYPE>>
{
	protected final Map<KEY_TYPE, Field<VALUE_TYPE>> fields = new HashMap<KEY_TYPE, Field<VALUE_TYPE>>();
	protected EditorDelegate<Map<KEY_TYPE, VALUE_TYPE>> delegate;

	protected static final int LABELWIDTH = 25;
	protected static final int SPACERWIDTH = 10;

	protected final List<KEY_TYPE> allPossibleLocales;

	private int lineHeight = 26;

	private boolean enabled = true;

	private final List<EditorError> errors = new ArrayList<EditorError>();

	protected abstract List<KEY_TYPE> getAllPossibleKeys();

	protected abstract String getKeyLabel(KEY_TYPE key);

	/**
	 * Constructor
	 */
	public DkLocalizedField()
	{
		this(26);
	}

	/**
	 * Constructor
	 * 
	 * @param lineHeight
	 */
	public DkLocalizedField(final int lineHeight)
	{
		super(null);
		this.lineHeight = lineHeight;
		setErrorSupport(new SideErrorHandler(this)
		{
			@Override
			public void markInvalid(final List<EditorError> errors)
			{
				DkLocalizedField.this.errors.addAll(errors);
				super.markInvalid(errors);
			}

			@Override
			public void clearInvalid()
			{
				DkLocalizedField.this.errors.clear();
				super.clearInvalid();
			}
		});

		allPossibleLocales = getAllPossibleKeys();

		final FlowLayoutContainer flowLayoutContainer = new FlowLayoutContainer();

		for (final KEY_TYPE locale : allPossibleLocales)
		{
			final HBoxLayoutContainer line = new HBoxLayoutContainer();
			line.getElement().getStyle().setMarginBottom(2, Unit.PX);
			line.getElement().getStyle().setMarginLeft(5, Unit.PX);
			final InlineLabel label = new InlineLabel(getKeyLabel(locale));
			label.setWidth(20 + "px");
			final Field<VALUE_TYPE> field = createField();
			fields.put(locale, field);
			line.add(label);
			line.add(new DkSpacer(SPACERWIDTH));
			line.add(field);
			field.setHeight(lineHeight);
			line.setHeight(lineHeight);
			flowLayoutContainer.add(line);
		}
		setWidget(flowLayoutContainer);

		flowLayoutContainer.addResizeHandler(new ResizeHandler()
		{
			@Override
			public void onResize(final ResizeEvent event)
			{
				final int width = event.getWidth();
				if (width > 0)
				{
					final int fieldWidth = width - LABELWIDTH - SPACERWIDTH;
					for (final Field<VALUE_TYPE> widget : fields.values())
					{
						widget.setWidth(fieldWidth);
					}
				}
			}
		});
	}

	/**
	 * Get field for given key type
	 * 
	 * @param keyType
	 * @return a {@link Field}
	 */
	public Field<VALUE_TYPE> getField(final KEY_TYPE keyType)
	{
		return fields.get(keyType);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.editor.client.ValueAwareEditor#flush()
	 */
	@Override
	public void flush()
	{
		if (delegate == null)
		{
			return;
		}
		else if (forceInvalidText != null)
		{
			delegate.recordError(forceInvalidText, "", this);
		}
		else
		{
			validate();
			for (final EditorError e : errors)
			{
				delegate.recordError(e.getMessage(), e.getValue(), this);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sencha.gxt.widget.core.client.form.AdapterField#setDelegate(com.google.gwt.editor.client.EditorDelegate)
	 */
	@Override
	public void setDelegate(final EditorDelegate<Map<KEY_TYPE, VALUE_TYPE>> delegate)
	{
		super.setDelegate(delegate);
		this.delegate = delegate;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.gwt.editor.client.ValueAwareEditor#onPropertyChange(java.lang.String[])
	 */
	@Override
	public void onPropertyChange(final String... paths)
	{
		// Nothing to be done here.
	}

	@Override
	public void setValue(final Map<KEY_TYPE, VALUE_TYPE> value)
	{
		for (final Entry<KEY_TYPE, Field<VALUE_TYPE>> entry : fields.entrySet())
		{
			entry.getValue().setValue(value == null ? null : value.get(entry.getKey()));
		}
	}

	@Override
	public Map<KEY_TYPE, VALUE_TYPE> getValue()
	{
		final Map<KEY_TYPE, VALUE_TYPE> value = new HashMap<KEY_TYPE, VALUE_TYPE>();
		for (final Entry<KEY_TYPE, Field<VALUE_TYPE>> entry : fields.entrySet())
		{
			VALUE_TYPE valueEntry = entry.getValue().getValue();
			if (entry.getValue().getValue() instanceof String)
			{
				valueEntry = DkStringUtils.hasText((String) entry.getValue().getValue()) ? entry.getValue().getValue() : null;
			}
			value.put(entry.getKey(), valueEntry);
		}
		return value;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sencha.gxt.widget.core.client.Component#setEnabled(boolean)
	 */
	@Override
	public void setEnabled(final boolean enabled)
	{
		for (final Field<VALUE_TYPE> field : fields.values())
		{
			field.setEnabled(enabled);
		}
		this.enabled = enabled;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sencha.gxt.widget.core.client.Component#isEnabled()
	 */
	@Override
	public boolean isEnabled()
	{
		return enabled;
	}

	/**
	 * @return the lineHeight
	 */
	public int getLineHeight()
	{
		return lineHeight;
	}

	/**
	 * @param lineHeight
	 *           the lineHeight to set
	 */
	public void setLineHeight(final int lineHeight)
	{
		this.lineHeight = lineHeight;
	}

	protected abstract Field<VALUE_TYPE> createField();

	private final BlurHandler validationBlurHandler = new BlurHandler()
	{
		@Override
		public void onBlur(final BlurEvent event)
		{
			validate();
		}
	};

	/**
	 * Sets this field value for given language mandatory.
	 * 
	 * @param mandatoryForKey
	 *           an implementation of {@link MandatoryForKeys}
	 * @param mandatory
	 *           a boolean indicating if it is mandatory or not
	 */
	public void setMandatory(final MandatoryForKeys<KEY_TYPE> mandatoryForKey, final boolean mandatory)
	{
		getValidators().clear();// We won't add any other validator anyway ... :D
		if (mandatory && mandatoryForKey != null)
		{
			addValidator(new Validator<Map<KEY_TYPE, VALUE_TYPE>>()
			{
				@Override
				public List<EditorError> validate(final Editor<Map<KEY_TYPE, VALUE_TYPE>> editor,
						final Map<KEY_TYPE, VALUE_TYPE> value)
				{
					final Collection<KEY_TYPE> mandatoryForKeys = mandatoryForKey.getKeys();
					if (mandatoryForKeys != null)
					{
						for (final KEY_TYPE key : mandatoryForKeys)
						{
							final VALUE_TYPE valueForKey = value.get(key);
							if (!DkStringUtils.hasText(DkStringUtils.toString(valueForKey)))
							{
								final List<EditorError> errors = new ArrayList<EditorError>();
								errors.add(new DefaultEditorError(editor, DefaultMessages.getMessages().textField_blankText(), ""));
								return errors;
							}
						}
					}
					return null;
				}
			});
			for (final Field<VALUE_TYPE> field : fields.values())
			{
				field.addBlurHandler(validationBlurHandler);
			}
		}
	}

	/**
	 * Interface providing method to retrieve the collection of language keys for which this field is mandatory.
	 * 
	 * @author tcaselli
	 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
	 * @param <KEY_TYPE>
	 */
	public static interface MandatoryForKeys<KEY_TYPE>
	{
		/**
		 * @return a collection of language keys for which this field is mandatory.
		 */
		Collection<KEY_TYPE> getKeys();
	}
}
